package com.niiwoo.civet.trade.service.local.newRefund.dispatch;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.niiwoo.civet.account.dto.common.WithholdOrderDTO;
import com.niiwoo.civet.account.dto.request.ApplyWithholdRequestDTO;
import com.niiwoo.civet.account.dto.response.AccountBaseResponse;
import com.niiwoo.civet.account.enums.WithholdOrderStatusEnum;
import com.niiwoo.civet.account.service.AccountDubboService;
import com.niiwoo.civet.account.service.trade.WithholdDubboService;
import com.niiwoo.civet.base.enums.OrderTradeSourceEnum;
import com.niiwoo.civet.base.enums.RefundTypeEnum;
import com.niiwoo.civet.trade.constant.MqConstant;
import com.niiwoo.civet.trade.constant.TradeMqConstant;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.*;
import com.niiwoo.civet.trade.dto.newRefund.RefundRepaymentDTO;
import com.niiwoo.civet.trade.dto.newRefund.RefundStateDTO;
import com.niiwoo.civet.trade.dto.request.RefundWithholdRequestDTO;
import com.niiwoo.civet.trade.dto.response.WithholdResultDTO;
import com.niiwoo.civet.trade.enums.refund.RefundFundTypeEnum;
import com.niiwoo.civet.trade.service.local.newRefund.notify.dto.RefundNotifyMsgDTO;
import com.niiwoo.civet.trade.service.local.newRefund.notify.enums.RefundNotifyRefundStatusEnum;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.RefundForRepaymentService;
import com.niiwoo.civet.trade.service.local.newRefund.refundDeal.core.FeeCalcForRefundService;
import com.niiwoo.civet.trade.service.local.refund.CommonLocalService;
import com.niiwoo.tripod.consumer.helper.Exceptions;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class WithholdLocalService
{
    @Autowired
    protected RabbitTemplate rabbitTemplate;
    @Autowired
    protected SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    protected CommonLocalService commonLocalService;
    @Autowired
    protected RefundWithholdOrderMapperExt refundWithholdOrderMapperExt;
    @Reference(version = "1.0.0")
    protected AccountDubboService accountDubboService;
    @Autowired
    protected ProjectBorrowerPlanNewMapperExt projectBorrowerPlanNewMapperExt;
    @Autowired
    private RedissonClient redissonClient;
    @Reference(version = "1.0.0")
    protected WithholdDubboService withholdDubboService;
    @Autowired
    RefundForRepaymentService refundForRepaymentService;
    @Autowired
    protected FeeCalcForRefundService feeCalcForRefundService;
    @Autowired
    protected ProjectMapperExt projectMapperExt;
    @Autowired
    ReplaceRepaymentConfigMapperExt repaymentConfigMapperExt;
    @Autowired
    RefundValidateService validateService;
    @Autowired
    private WithholdConfigMapper withholdConfigMapper;
    @Autowired
    RefundOrderMapperExt refundOrderMapperExt;
    @Autowired
    protected ProjectBorrowerSummaryMapperExt summaryMapperExt;


    /**
     * 当前是否已还款
     * @param projectId
     * @return
     */
    boolean isCurrentRefunded(Long projectId)
    {
        ProjectBorrowerSummary summary = summaryMapperExt.select(projectId,commonLocalService.day2String(commonLocalService.getCurrentDate()));
        return summary == null ? true : false;
    }

    /**
     * 订单是否处理中
     * @param projectId
     * @return
     */
    boolean isOrderProcessing(Long projectId)
    {
        return refundWithholdOrderMapperExt.countWithholdProcessing(projectId) > 0
                || refundWithholdOrderMapperExt.countRefunding(projectId) > 0
                || refundOrderMapperExt.selectInProgressOrderCount(projectId) > 0;
    }


    /**
     * 机构是否支持代扣
     * @param orgId
     * @return
     */
    boolean isWithHold(String orgId)
    {
        WithholdConfig withholdConfig = withholdConfigMapper.selectByPrimaryKey(orgId);
        if(null != withholdConfig && Byte.valueOf("1").equals(withholdConfig.getWithhold()))
        {
            return true;
        }
        return false;
    }


    /**
     * 计算还款金额
     * @param project
     * @param requestDTO
     * @return
     */
    RefundStateDTO caculateAmount(Project project,RefundWithholdRequestDTO requestDTO)
    {
        RefundStateDTO feeDTO =  null;
        if (RefundWithholdRequestDTO.WithHoldOptTypeEnum.PERIODS == requestDTO.getOptType())
        {
            if (!refundForRepaymentService.checkPeriodsIsLegal(project.getProjectId(),requestDTO.getPeriods()))
                Exceptions.throwBizException("TRD_800015","指定期数异常");

            feeDTO = feeCalcForRefundService.caculateAmountByPeriods(project.getProjectId(),requestDTO.getPeriods());
        }
        else
        {
            feeDTO = feeCalcForRefundService.caculateAmountRepaymentForAPP(project.getProjectId(), requestDTO.getRefundType());
        }

        return feeDTO;
    }

    /**
     * 代扣
     * @param requestDTO
     * @return
     * @throws Exception
     */
    public Long withhold(RefundWithholdRequestDTO requestDTO) throws Exception
    {
        Assert.notNull(requestDTO,"refundWithholdRequestDTO must not be null");
        validateService.validate(requestDTO);

        Project project = projectMapperExt.selectByPrimaryKey(requestDTO.getProjectId());
        if (!isWithHold(project.getOrgId()))  Exceptions.throwBizException("TRD_800011","该标的所属机构暂不支持代扣");
        requestDTO.setBorrowUserId(project.getBorrowUserId());

        //计算标的应还款金额
        //待还款金额 调用接口计算 代扣后需要做提前结清或足额划扣
        RefundStateDTO feeDTO =  caculateAmount(project,requestDTO);
        Assert.notNull(feeDTO,"calculate Amount error");

        BigDecimal repayAmount =feeDTO.getAmount();
        log.info("WithholdLocalService ->withhold calculate:projectId={},repayAmount={}",project.getProjectId(),repayAmount);
        if (repayAmount.compareTo(BigDecimal.ZERO) <= 0)  Exceptions.throwBizException("TRD_800014","标的当前不需还款");

        //根据用户id获取账户信息
        AccountBaseResponse accountBaseResponse = accountDubboService.loadAccountById(project.getBorrowAccountId());
        Assert.notNull(accountBaseResponse,"borrower account must not be null");

        //计算实际充值金额
        repayAmount = repayAmount.subtract(accountBaseResponse.getAvailableAmount());
        log.info("WithholdLocalService ->withhold calculate repayAmount:projectId={},availableAmount={},repayAmount={}", project.getProjectId(),accountBaseResponse.getAvailableAmount(),repayAmount);

        //余额足够直接还款，则不进行自动充值
        if(repayAmount.compareTo(BigDecimal.ZERO) <= 0)
        {
            return this.refund(requestDTO);
        }

        //判断神盾输入的金额是否满足条件
        if (requestDTO.getAmount() != null)
        {
            // 指定的代扣金额小于等于实际需要代扣的金额，则使用指定代扣金额
            if (requestDTO.getAmount().compareTo(repayAmount) <= 0) {
                repayAmount = requestDTO.getAmount();
            } else {
                // 指定的代扣金额大于实际需要代扣金额，并且是非批量代扣代扣金额，则不允许代扣
                // 批量代扣则时，如果指定金额大于实际代扣金额则使用实际代扣金额  repayAmount
                if(StringUtils.isBlank(requestDTO.getBatchNo()) && !RefundWithholdRequestDTO.WithHoldTradeSourceEnum.OVERDUE_50_DAY.equals(requestDTO.getTradeSource())) {
                    Exceptions.throwBizException("TRD_800008", "代扣金额不合法");
                }
            }
        }

        RLock lock = null;
        Long orderId = null;
        try
        {
            lock = redissonClient.getLock("WithholdLocalService:withhold:"+requestDTO.getProjectId());
            lock.lock();
            log.info("WithholdLocalService ->withhold lock:projectId={}",project.getProjectId());
            orderId  = ((WithholdLocalService)AopContext.currentProxy()).
                    generatingOrderAndSendMsg(requestDTO,repayAmount,accountBaseResponse.getAccountId());
            log.info("WithholdLocalService ->withhold generatingOrderAndSendMsg:projectId={},orderId={}",project.getProjectId(),orderId);
        }
        finally
        {
            if (lock != null) lock.unlock();
            log.info("WithholdLocalService ->withhold unlock:projectId={}",project.getProjectId());
        }

        return orderId;
    }


    /**
     * 发消息到账户中心
     * @param accountId
     * @param amount
     * @param orderId
     */
    protected void sendOrderMsg(Long accountId, BigDecimal amount, Long orderId,Long payChannelId,Byte tradeSource, String batchNo)
    {
        try
        {
            //发消息到账户中心代扣
            ApplyWithholdRequestDTO requestDTO = new ApplyWithholdRequestDTO();
            requestDTO.setAccountId(accountId);
            requestDTO.setAmount(amount);
            requestDTO.setOrderId(orderId);
            requestDTO.setPayChannelId(payChannelId);
            requestDTO.setTradeSource(tradeSource);
            requestDTO.setBatchNo(batchNo);

            log.info("RefundWithholdLocalService->sendOrderMsg orderId={},msg={}",orderId,requestDTO);
            rabbitTemplate.convertAndSend(MqConstant.WITHHOLD_APPLY_EXCHANGE, MqConstant.WITHHOLD_APPLY_DATA_ROUTING, requestDTO);
        }
        catch (Exception e)
        {
            log.error("RefundWithholdLocalService -> sendOrderMsg error,orderId={}",orderId,e);
        }
    }


    /**
     * 生成订单，发送消息
     * @param requestDTO
     * @param repayAmount
     * @param accountId
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED,rollbackFor =Exception.class)
    public Long generatingOrderAndSendMsg(RefundWithholdRequestDTO requestDTO, BigDecimal repayAmount, Long accountId)
    {
        //查找处理中的订单
        if (isOrderProcessing(requestDTO.getProjectId())) Exceptions.throwBizException("TRD_800017","订单处理中，请勿重复提交");

        //非批量代扣(BatchNo为空)标的限制每天只能代扣一次；
        if (refundWithholdOrderMapperExt.countWithholdSuccess(requestDTO.getProjectId()) >0 && StringUtils.isBlank(requestDTO.getBatchNo())
                && !RefundWithholdRequestDTO.WithHoldTradeSourceEnum.OVERDUE_50_DAY.equals(requestDTO.getTradeSource()))
            Exceptions.throwBizException("TRD_800018","今天已有代扣记录");

        //生成代扣订单
        RefundWithholdOrder insertOrder = new RefundWithholdOrder();
        BeanUtils.copyProperties(requestDTO,insertOrder);
        insertOrder.setId(snowflakeIdWorker.nextId());
        insertOrder.setCurrentDate(commonLocalService.getCurrentDate());
        insertOrder.setAmount(repayAmount);
        insertOrder.setAccountId(accountId);
        insertOrder.setTradeSource(requestDTO.getTradeSource().getValue());
        Date date = new Date();
        insertOrder.setStatus(RefundWithholdRequestDTO.WithholdOrderProcessingEnum.PROCESSING.getValue());
        insertOrder.setCreateTime(date);
        insertOrder.setUpdateTime(date);
        insertOrder.setOrgRequestNo(requestDTO.getOrgRequestNo());
        insertOrder.setPeriods(StringUtils.join(requestDTO.getPeriods(),","));
        insertOrder.setOptType(requestDTO.getOptType().getValue());
        insertOrder.setBatchNo(requestDTO.getBatchNo());
        insertOrder.setRefundType(requestDTO.getRefundType().getRefundType());
        refundWithholdOrderMapperExt.insertSelective(insertOrder);
        //发消息到账户中心
        sendOrderMsg(accountId,repayAmount,insertOrder.getId(),requestDTO.getPayChannelId(),requestDTO.getTradeSource().getValue(),requestDTO.getBatchNo());

        return  insertOrder.getId();
    }


    /**
     * 代扣结果处理
     * @param withholdResultDTO
     */
    @Transactional(isolation = Isolation.READ_COMMITTED,rollbackFor = Exception.class)
    public void result(WithholdResultDTO withholdResultDTO)
    {
        Assert.notNull(withholdResultDTO,"WithholdResultDTO not be null");
        validateService.validate(withholdResultDTO);
        log.info("RefundWithholdLocalService -> result,orderId={},param={}", withholdResultDTO.getOrderId(),withholdResultDTO);

        //同步控制
        RefundWithholdOrder orderFopUpdate = refundWithholdOrderMapperExt.selectByPrimaryKeyForUpdate(withholdResultDTO.getOrderId());
        if (orderFopUpdate == null)
        {
            log.info("RefundWithholdLocalService -> result：RefundWithholdOrder is not find,orderId={}",withholdResultDTO.getOrderId());
            return;
        }

        if (orderFopUpdate.getStatus() > RefundWithholdRequestDTO.WithholdOrderProcessingEnum.PROCESSING.getValue())
            return;

        orderFopUpdate.setErrorCode(withholdResultDTO.getCode());
        orderFopUpdate.setErrorMessage(withholdResultDTO.getMessage());
        orderFopUpdate.setRefundStatus(withholdResultDTO.isSuccess()
                ? RefundWithholdRequestDTO.WithholdOrderProcessingEnum.PROCESSING.getValue()
                : 0);
        orderFopUpdate.setStatus(withholdResultDTO.isSuccess()
                ? RefundWithholdRequestDTO.WithholdOrderProcessingEnum.SUCCESS.getValue()
                : RefundWithholdRequestDTO.WithholdOrderProcessingEnum.FAILURE.getValue());
        orderFopUpdate.setUpdateTime(new Date());

        // 逾期50天代扣 如果代扣成功，还款状态直接更新成成功
        if (RefundWithholdRequestDTO.WithHoldTradeSourceEnum.OVERDUE_50_DAY.getValue().equals(orderFopUpdate.getTradeSource())) {
            orderFopUpdate.setRefundStatus(withholdResultDTO.isSuccess()
                    ? RefundWithholdRequestDTO.WithholdOrderProcessingEnum.SUCCESS.getValue()
                    : 0);
        }

        refundWithholdOrderMapperExt.updateByPrimaryKeySelective(orderFopUpdate);

        if (withholdResultDTO.isSuccess()) {
            if (RefundWithholdRequestDTO.WithHoldTradeSourceEnum.OVERDUE_50_DAY.getValue().equals(orderFopUpdate.getTradeSource())) {
                //逾期50天的扣款成功则发消息继续扣款
                RefundRepaymentDTO repaymentDTO = new RefundRepaymentDTO();
                repaymentDTO.setProjectId(orderFopUpdate.getProjectId());
                repaymentDTO.setCurrentDate(orderFopUpdate.getCurrentDate());

                log.info("OVERDUE_50_DAY withhold success requeue->sendOrderMsg orderId={},repaymentDTO={}", withholdResultDTO.getOrderId(), JSON.toJSON(repaymentDTO));
                rabbitTemplate.convertAndSend(MqConstant.WITHHOLD_APPLY_EXCHANGE, MqConstant.WITHHOLD_OVERDUE_50_DAY_SUCCESS_ROUTING, repaymentDTO);
            } else {
                //触发还款
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                    @Override
                    public void afterCommit() {
                        try {
                            log.info("RefundWithholdLocalService -> result refund request:orderId={},withholdOrder={}", orderFopUpdate.getId(), orderFopUpdate);
                            RefundOrder refundOrder = null;
                            RefundTypeEnum refundTypeEnum = RefundTypeEnum.enumOf(orderFopUpdate.getRefundType());
                            if (RefundWithholdRequestDTO.WithHoldOptTypeEnum.PERIODS.getValue().equals(orderFopUpdate.getOptType())) {
                                List<String> periodsList = Arrays.asList(orderFopUpdate.getPeriods().split(","));
                                List<Integer> periods = periodsList.stream().map(s -> Integer.parseInt(s)).collect(Collectors.toList());
                                refundOrder = refundForRepaymentService.refundByPeriods(
                                        orderFopUpdate.getProjectId(),
                                        OrderTradeSourceEnum.WITHHOLD,
                                        refundTypeEnum,
                                        orderFopUpdate.getCurrentDate(),
                                        periods,
                                        orderFopUpdate.getOrgRequestNo());
                            } else {
                                refundOrder = refundForRepaymentService.refundForRepayment(
                                        orderFopUpdate.getProjectId(),
                                        OrderTradeSourceEnum.WITHHOLD,
                                        refundTypeEnum,
                                        orderFopUpdate.getCurrentDate(),
                                        orderFopUpdate.getOrgRequestNo());
                            }
                            log.info("RefundWithholdLocalService -> result refund response:projectId={},refundOrder={}", orderFopUpdate.getProjectId(), refundOrder);
                            if (refundOrder != null && refundOrder.getId() != null) {
                                //维护还款订单号
                                orderFopUpdate.setRefundOrderId(refundOrder.getId());
                                orderFopUpdate.setRefundStatus(RefundWithholdRequestDTO.WithholdOrderProcessingEnum.SUCCESS.getValue());
                                orderFopUpdate.setUpdateTime(new Date());
                                refundWithholdOrderMapperExt.updateByPrimaryKeySelective(orderFopUpdate);
                            }
                        } catch (Exception e) {
                            log.error("RefundWithholdLocalService -> result refund error:orderId={}", orderFopUpdate.getId(), e);
                            orderFopUpdate.setRefundStatus(RefundWithholdRequestDTO.WithholdOrderProcessingEnum.FAILURE.getValue());
                            orderFopUpdate.setUpdateTime(new Date());
                            if (e instanceof BizException) {
                                orderFopUpdate.setErrorCode(((BizException) e).getErrorCode());
                                orderFopUpdate.setErrorMessage(e.getMessage());
                            } else {
                                orderFopUpdate.setErrorMessage("还款失败");
                            }
                            refundWithholdOrderMapperExt.updateByPrimaryKeySelective(orderFopUpdate);
                        }
                    }
                });
            }
        } else {
			withholdFailNotify(orderFopUpdate.getProjectId(), orderFopUpdate.getOrgRequestNo(),
					orderFopUpdate.getTradeSource(), withholdResultDTO.getMessage());
        }
    }

    /**
     * 还款
     * @param requestDTO
     */
    Long refund(RefundWithholdRequestDTO requestDTO)
    {
        log.info("WithholdLocalService ->withhold refund request:projectId={},requestDTO={}",requestDTO.getProjectId(),requestDTO);

        //查询代扣处理中的订单
        if (isOrderProcessing(requestDTO.getProjectId())) Exceptions.throwBizException("TRD_800017","订单处理中，请勿重复提交");

        //如果是代扣还款，需求校验是否当天有代扣记录
        if (RefundFundTypeEnum.WITHHOLD_REFUND.equals(requestDTO.getRefundFundTypeEnum())
                && refundWithholdOrderMapperExt.countRefundSuccess(requestDTO.getProjectId()) >0) {
            Exceptions.throwBizException("TRD_800018", "今天已有代扣记录");
        }

        RefundOrder refundOrder = null;
        if(RefundWithholdRequestDTO.WithHoldOptTypeEnum.PERIODS == requestDTO.getOptType())
        {
            refundOrder = refundForRepaymentService.refundByPeriods(
                    requestDTO.getProjectId(),
                    OrderTradeSourceEnum.APP,
                    requestDTO.getRefundType(),
                    commonLocalService.getCurrentDate(),
                    requestDTO.getPeriods(),
                    requestDTO.getOrgRequestNo());
        }
        else
        {
            refundOrder = refundForRepaymentService.refundForRepayment(
                    requestDTO.getProjectId(),
                    OrderTradeSourceEnum.APP,
                    requestDTO.getRefundType(),
                    commonLocalService.getCurrentDate(),
                    requestDTO.getOrgRequestNo());
        }
        log.info("WithholdLocalService ->withhold refund response:projectId={},refundOrder={}",requestDTO.getProjectId(),refundOrder);
        return refundOrder.getId();
    }

    /**
     * 代扣补单
     */
    public void withholdSupplement(RefundWithholdOrder withholdOrder)
    {
        log.info("RefundWithholdLocalService -> withholdSupplement request:orderId={},withholdOrder={}", withholdOrder.getId(),withholdOrder);
        //查询账户中心，订单状态
        WithholdOrderDTO withholdOrderDTO = null;
        try
        {
            withholdOrderDTO = withholdDubboService.queryWithholdOrderByApplyOrderId(withholdOrder.getId());
        }
        catch (Exception e)
        {
            log.error("RefundWithholdLocalService -> withholdSupplement error:orderId={},", withholdOrder.getId(),e);
            return;
        }


        if (withholdOrderDTO == null)
        {
            //账户中心没有收到请求，需要重新发送
            //sendOrderMsg(withholdOrder.getAccountId(),withholdOrder.getAmount(),withholdOrder.getId());
        }
        else if(WithholdOrderStatusEnum.SUCCESS == withholdOrderDTO.getStatus()
                || WithholdOrderStatusEnum.FAIL == withholdOrderDTO.getStatus())
        {
            //判断状态
            //更新订单状态
            WithholdResultDTO resultDTO = new WithholdResultDTO();
            resultDTO.setCode(WithholdOrderStatusEnum.SUCCESS == withholdOrderDTO.getStatus()
                    ? "0000"
                    : "1000");
            resultDTO.setOrderId(withholdOrder.getId());
            try
            {
                ((WithholdLocalService)AopContext.currentProxy()).result(resultDTO);
            }
            catch (Exception e)
            {
                log.error("RefundWithholdLocalService -> withholdSupplement error:orderId={}", resultDTO.getOrderId(),e);
            }
        }
    }
    
    /**
     * 调用帐户代扣失败
     *  
     * @param projectId
     * @param orgRequestNo
     * @param traderSource
     * @param failMessage
     */
    public void withholdFailNotify(Long projectId, String orgRequestNo, Byte traderSource, String failMessage) {
    	RefundNotifyMsgDTO refundNotifyMsgDTO = new RefundNotifyMsgDTO();
    	refundNotifyMsgDTO.setRefundStatus(RefundNotifyRefundStatusEnum.REPAPYMENT_FAIL.getStatus());
		refundNotifyMsgDTO.setProjectId(projectId);
		refundNotifyMsgDTO.setOrgRequestNo(orgRequestNo);
		refundNotifyMsgDTO.setWithholdTradeSource(traderSource);
		refundNotifyMsgDTO.setFailMessage(failMessage);
		rabbitTemplate.convertAndSend(TradeMqConstant.REFUND_NOTIFY_HANDLE_EXCHANGE,
				TradeMqConstant.REFUND_NOTIFY_HANDLE_ROUTINGKEY, refundNotifyMsgDTO);
    }
}
